#include "../src/parse.hh"

#include <assert.h>
#include <cmath>

namespace
{
  using namespace playerpiano;

char*
random_feature_string (void)
{
  unsigned int len = 1.0 + 10.0 * drand48 ();
  char buf[1 + len];

  for (unsigned int n = 0; n < len; ++n)
    {
      do
        {
          buf[n] = lrand48 ();
        }
      while (! isgraph (buf[n]) || (n == 0 && buf[n] == '|'));
    }
  buf[len] = '\0';

  return strdup (buf);
}

char*
random_namespace_string (void)
{
  unsigned int len = 1.0 + 10.0 * drand48 ();
  char buf[1 + len];

  for (unsigned int n = 0; n < len; ++n)
    {
      do
        {
          buf[n] = lrand48 ();
        }
      while (! isgraph (buf[n]));
    }
  buf[len] = '\0';

  return strdup (buf);
}

char*
random_worker_string (void)
{
  unsigned int len = 1.0 + 10.0 * drand48 ();
  char buf[1 + len];

  for (unsigned int n = 0; n < len; ++n)
    {
      do
        {
          buf[n] = lrand48 ();
        }
      while (! isgraph (buf[n]) || buf[n] == ',');
    }
  buf[len] = '\0';

  return strdup (buf);
}

void
random_nonpipe (char*        tag,
                unsigned int len)
{
  for (unsigned int n = 0; n < len; ++n)
    {
      do
        {
          tag[n] = lrand48 () % 256;
        }
      while (! isgraph (tag[n]) || tag[n] == '|');
    }
}

void
print_file (FILE* from,
            FILE* to)
{
  char buf[65536];

  fseek (from, SEEK_SET, 0);

  while (fgets (buf, sizeof (buf), from))
    {
      fprintf (to, "%s\n", buf);
    }
}

bool
fuzzy_equal (float a,
             float b)
{
  return fabs (a - b) <= 1e-5 * (1.0 + fabs (a) + fabs (b));
}

bool
fuzzy_equal (const FeatureSet& a,
             const FeatureSet& b)
{
  if (a.size () != b.size ())
    {
      return false;
    }

  for (unsigned int i = 0; i < a.size (); ++i)
    {
      if (a[i].feature != b[i].feature)
        {
          return false;
        }

      if (! fuzzy_equal (a[i].value, b[i].value))
        {
          return false;
        }
    }

  return true;
}

bool
fuzzy_equal (const IndexedFeatureSet& a,
             const IndexedFeatureSet& b)
{
  for (unsigned int i = 0; i < 256; ++i)
    {
      if (! fuzzy_equal (a[i], b[i]))
        {
          return false;
        }
    }

  return true;
}

void
print_ratings (const std::vector<Rating>& v,
               FILE*                      out)
{
  for (unsigned int i = 0; i < v.size (); ++i)
    {
      fprintf (out, "{ %u, %u },", v[i].worker, v[i].label);
    }
}

void
print_feature_set (const FeatureSet& v,
                   FILE*             out)
{
  for (unsigned int i = 0; i < v.size (); ++i)
    {
      fprintf (out, "{ %llu, %f },", (unsigned long long) v[i].feature, v[i].value);
    }
}

void
print_indexed_feature_set (const IndexedFeatureSet& a,
                           FILE*                    out)
{
  for (unsigned int i = 0; i < 256; ++i)
    {
      if (a[i].size ())
        {
          fprintf (out, " %u => ", i);
          print_feature_set (a[i], out);
        }
    }
}

void
test_parse_once (void)
{
  FILE* f = tmpfile ();
  unsigned int n_labels = 1.0 + 50.0 * drand48 ();
  unsigned int n_workers = 1.0 + 50.0 * drand48 ();
  unsigned int n_ratings = 0.0 + 50.0 * drand48 ();
  unsigned int n_spaces = 0.0 + 5.0 * drand48 ();
  int clamp = (drand48 () < 0.5) ? -1 : n_labels * drand48 ();
  bool did_clamp = false;
  bool first_print = true;
  Rating ratings[n_ratings];
  IndexedFeatureSet feature_set;
  unsigned int n_tag = 0.0 + 50.0 * drand48 ();
  float importance_weight = (drand48 () < 0.5) ? -1 : 10.0 * drand48 ();
  char tag[n_tag + 1];

  random_nonpipe (tag, n_tag);
  tag[n_tag] = '\0';

  for (unsigned int n = 0; n < n_ratings; ++n)
    {
      unsigned int label = n_labels * drand48 ();
      ratings[n].label = label;

      if (! did_clamp && clamp >= 0 && drand48 () < 0.25)
        {
          fprintf (f, "%s%d", (first_print) ? "" : ",", clamp);
          did_clamp = true;
          first_print = false;
        }

      if (drand48 () < 0.5)
        {
          unsigned int worker = lrand48 ();
          ratings[n].worker = worker % n_workers;

          fprintf (f, "%s%u:%u", (first_print) ? "" : ",", worker, label);
          first_print = false;
        }
      else
        {
          char* worker = random_worker_string ();
          ratings[n].worker = hash (worker, 0) % n_workers;

          fprintf (f, "%s%s:%u", (first_print) ? "" : ",", worker, label);
          first_print = false;

          free (worker);
        }
    }

  if (! did_clamp && clamp >= 0)
    {
      fprintf (f, "%s%d", (first_print) ? "" : ",", clamp);
      did_clamp = true;
      first_print = false;
    }

  if (importance_weight >= 0)
    {
      fprintf (f, " %f %s|", importance_weight, tag);
    }
  else
    {
      fprintf (f, " %s|", tag);
    }

  for (unsigned int n = 0; n < n_spaces; ++n)
    {
      char* space = random_namespace_string ();
      unsigned int ns = static_cast<unsigned int> ((space) ? space[0] : 0);
      uint64_t seed = (space) ? hash (space, 0) : hash ("", 0);
      float nsweight = (drand48 () < 0.5) ? 1 : drand48 ();

      fprintf (f, "%s%s", 
               (n > 0) ? " |" : "", 
               space ? space : "");

      if (nsweight != 1 || (space && strchr (space, ':')))
        {
          fprintf (f, ":%f", nsweight);
        }

      unsigned int n_space_features = 0.0 + 10.0 * drand48 ();

      for (unsigned int k = 0; k < n_space_features; ++k)
        {
          Feature feat;

          if (drand48 () < 0.5)
            {
              unsigned int feature = lrand48 ();
              float value = (drand48 () < 0.5) ? 1 : drand48 ();
              feat.feature = (feature + seed);
              feat.value = nsweight * value;

              if (feat.value == 1)
                {
                  fprintf (f, " %u", feature);
                }
              else
                {
                  fprintf (f, " %u:%f", feature, value);
                }
            }
          else
            {
              char* feature = random_feature_string ();
              float value = (drand48 () < 0.5) ? 1 : drand48 ();
              feat.feature = hash (feature, seed);
              feat.value = nsweight * value;
              if (feat.value == 1 && ! strchr (feature, ':'))
                {
                  fprintf (f, " %s", feature);
                }
              else
                {
                  fprintf (f, " %s:%f", feature, value);
                }
              free (feature);
            }

          feature_set[ns].push_back (feat);
        }
    }

  fprintf (f, "\n");

  fseek (f, SEEK_SET, 0);

  std::vector<Rating> the_ratings;
  IndexedFeatureSet the_feature_set;
  char the_tag[1024];
  int the_clamp = -6969;
  float the_importance_weight = -6969;
  int rv = parse_input_line (f,
                             the_ratings,
                             the_feature_set,
                             the_importance_weight,
                             the_tag,
                             sizeof (the_tag),
                             the_clamp,
                             n_labels,
                             n_workers);

  assert (rv == 0 || (print_file (f, stderr), 0));
  assert (the_ratings.size () == n_ratings || 
          (print_file (f, stderr), 
           fprintf (stderr, "the_ratings.size () = %zu ?= %u = n_ratings\n",
                    the_ratings.size (),
                    n_ratings),
           print_ratings (the_ratings, stderr),
           fprintf (stderr, "\n"),
           0));
  assert (strcmp (tag, the_tag) == 0);
  assert (memcmp (ratings, 
                  &the_ratings[0],
                  n_ratings * sizeof (ratings[0])) == 0 ||
          (print_file (f, stderr),
           print_ratings (the_ratings, stderr),
           fprintf (stderr, "\nratings do not match\n"),
           0));
  assert (clamp == the_clamp || 
          (print_file (f, stderr),
           fprintf (stderr, "clamp: %d ?= %d\n", clamp, the_clamp),
           0));
  assert (fuzzy_equal ((importance_weight < 0) ? 1 : importance_weight, 
                       the_importance_weight) ||
          (print_file (f, stderr),
           fprintf (stderr,
                    "importance_weight: %g ?= %g\n",
                    importance_weight,
                    the_importance_weight),
           0));

  assert (fuzzy_equal (feature_set, the_feature_set) ||
          (print_file (f, stderr),
           print_indexed_feature_set (feature_set, stderr),
           fprintf (stderr, "\n"),
           print_indexed_feature_set (the_feature_set, stderr),
           fprintf (stderr, "\nfeature sets do not match\n"),
           0));

  fclose (f);
}

void
test_parse (void)
{
  for (unsigned int i = 0; i < 5000; ++i)
    {
      test_parse_once ();
    }
}

}

int 
main (void) 
{
  srand48 (69);

  test_parse ();

  return 0;
}
